package io.gitee.thghh.dynamic.liquibase.configuration;

import io.gitee.thghh.dynamic.liquibase.liquibase.DynamicSpringLiquibase;
import io.gitee.thghh.dynamic.liquibase.property.DynamicProperty;
import liquibase.changelog.visitor.ChangeExecListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.wiring.BeanWiringInfo;
import org.springframework.beans.factory.wiring.BeanWiringInfoResolver;
import org.springframework.beans.factory.wiring.ClassNameBeanWiringInfoResolver;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;

/**
 * Liquibase配置
 *
 * @author pengzhikang
 * @date 2024/11/28 9:33
 * @since 1.0.0
 */
@Slf4j
@Configuration
@ComponentScan(basePackages = "io.gitee.thghh.dynamic.liquibase")
@EnableConfigurationProperties(DynamicLiquibaseProperties.class)
@ConditionalOnProperty(prefix = DynamicLiquibaseProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicLiquibaseConfiguration implements DisposableBean {

    /** 后缀 */
    public static final String SPRING_LIQUIBASE = "SpringLiquibase";

    /** liquibase配置 */
    private final DynamicLiquibaseProperties properties;

    /** 监听器 */
    private final List<ChangeExecListener> listeners;

    /** 动态属性 */
    private final List<DynamicProperty> dynamicPropertys;

    /** bean工厂 */
    private final ConfigurableListableBeanFactory beanFactory;

    public DynamicLiquibaseConfiguration(DynamicLiquibaseProperties properties,
                                         List<ChangeExecListener> listeners,
                                         List<DynamicProperty> dynamicPropertys,
                                         ConfigurableListableBeanFactory beanFactory) {
        this.properties = properties;
        this.listeners = listeners;
        this.dynamicPropertys = dynamicPropertys;
        this.beanFactory = beanFactory;
    }

    /**
     * 后置处理器
     *
     * @author pengzhikang
     * @date 2024/12/12 10:20
     */
    @PostConstruct
    public void postProcessBeanFactory() {
        BeanWiringInfoResolver bwiResolver = new ClassNameBeanWiringInfoResolver();

        Map<String, LiquibaseProperties> datasourceMap = properties.getDatasource();
        for (Map.Entry<String, LiquibaseProperties> entry : datasourceMap.entrySet()) {
            String beanName = getBeanName(entry.getKey());
            LiquibaseProperties datasource = entry.getValue();
            if (!datasource.isEnabled()) {
                log.info("Init liquibase bean not enable.");
                continue;
            }

            log.info("Init liquibase bean {}", beanName);

            DynamicSpringLiquibase springLiquibase = springLiquibase(datasource);
            springLiquibase.setBeanName(beanName);

            BeanWiringInfo bwi = bwiResolver.resolveWiringInfo(springLiquibase);
            if (bwi == null) {
                // Skip the bean if no wiring info given.
                log.info("Skip the bean if no wiring info given.");
                continue;
            }

            // 注入到容器
            if (bwi.indicatesAutowiring() || bwi.isDefaultBeanName() && !beanFactory.containsBean(beanName)) {
                // Perform autowiring (also applying standard factory / post-processor callbacks).
                beanFactory.autowireBeanProperties(springLiquibase, bwi.getAutowireMode(), bwi.getDependencyCheck());
                beanFactory.initializeBean(springLiquibase, beanName);
            } else {
                // Perform explicit wiring based on the specified bean definition.
                beanFactory.configureBean(springLiquibase, beanName);
            }

            if (!beanFactory.containsBean(beanName)) {
                beanFactory.registerSingleton(beanName, springLiquibase);
            }
        }

        log.info("Init liquibase bean success");
    }

    /**
     * 构建liquibase实例
     *
     * @param properties 配置
     * @return {@link DynamicSpringLiquibase }
     * @author pengzhikang
     * @date 2024/12/11 19:08
     */
    private DynamicSpringLiquibase springLiquibase(LiquibaseProperties properties) {
        DynamicSpringLiquibase liquibase = new DynamicSpringLiquibase();
        liquibase.setDataSource(getDataSource(properties));
        liquibase.setChangeLog(properties.getChangeLog());
        liquibase.setClearCheckSums(properties.isClearChecksums());
        liquibase.setContexts(properties.getContexts());
        liquibase.setDefaultSchema(properties.getDefaultSchema());
        liquibase.setLiquibaseSchema(properties.getLiquibaseSchema());
        liquibase.setLiquibaseTablespace(properties.getLiquibaseTablespace());
        liquibase.setDatabaseChangeLogTable(properties.getDatabaseChangeLogTable());
        liquibase.setDatabaseChangeLogLockTable(properties.getDatabaseChangeLogLockTable());
        liquibase.setDropFirst(properties.isDropFirst());
        liquibase.setShouldRun(properties.isEnabled());
        liquibase.setLabelFilter(properties.getLabels());
        liquibase.setChangeLogParameters(properties.getParameters());
        liquibase.setRollbackFile(properties.getRollbackFile());
        liquibase.setTestRollbackOnUpdate(properties.isTestRollbackOnUpdate());
        liquibase.setTag(properties.getTag());
        liquibase.setResourceLoader(new DefaultResourceLoader());
        liquibase.addListeners(listeners);
        liquibase.addDynamicProperties(dynamicPropertys);
        return liquibase;
    }

    /**
     * 创建数据源
     *
     * @param properties 配置
     * @return {@link DataSource }
     * @author pengzhikang
     * @date 2024/12/11 19:08
     */
    private DataSource getDataSource(LiquibaseProperties properties) {
        DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
        builder.url(properties.getUrl());
        builder.username(properties.getUser());
        builder.password(properties.getPassword());
        builder.driverClassName(properties.getDriverClassName());
        return builder.build();
    }

    @Override
    public void destroy() {
        Map<String, LiquibaseProperties> datasourceMap = properties.getDatasource();
        for (String key : datasourceMap.keySet()) {
            String beanName = getBeanName(key);
            if (beanFactory.containsBean(beanName)) {
                DynamicSpringLiquibase liquibase = beanFactory.getBean(beanName, DynamicSpringLiquibase.class);
                liquibase.destroy();
            }
        }
    }

    /**
     * 获取bean名称
     *
     * @param liquibaseType liquibase类型
     * @return {@link String }
     * @author pengzhikang
     * @date 2024/12/16 13:22
     */
    public String getBeanName(String liquibaseType) {
        return liquibaseType + DynamicLiquibaseConfiguration.SPRING_LIQUIBASE;
    }
}
